<html>
 <head>
  <meta charset="UTF-8">
 </head>
 <body>
  <h1 data-lake-id="H2is9" id="H2is9"><span data-lake-id="u683942c9" id="u683942c9">典型回答</span></h1>
  <p data-lake-id="ufa22c3c4" id="ufa22c3c4"><br></p>
  <p data-lake-id="ua576b876" id="ua576b876"><span data-lake-id="u82f4928b" id="u82f4928b">在分布式系统中，虽然我们会使用各种分布式事务的方案，来保证各个系统之间的一致性。但是，很多时候往往事与愿违。</span></p>
  <p data-lake-id="uee9a2f78" id="uee9a2f78"><span data-lake-id="u1cdf9000" id="u1cdf9000">​</span><br></p>
  <p data-lake-id="u0feacdf1" id="u0feacdf1"><span data-lake-id="ued9c602a" id="ued9c602a">尤其是现在很多公司都采用最终一致性的方案，而所谓最终一致性，无论是本地消息表、事务消息、还是任务重试，系统之间的调用都是有可能失败的。</span></p>
  <p data-lake-id="u9cf0cfaa" id="u9cf0cfaa"><span data-lake-id="uff2bde4f" id="uff2bde4f">​</span><br></p>
  <p data-lake-id="ubd88381f" id="ubd88381f"><span data-lake-id="udb8c918c" id="udb8c918c">而一旦发生失败，就需要有一套机制来发现这些不一致的问题，这时候就需要做数据对账了。</span></p>
  <p data-lake-id="u1a3a3312" id="u1a3a3312"><span data-lake-id="ud6399228" id="ud6399228">​</span><br></p>
  <p data-lake-id="uadb8552b" id="uadb8552b"><span data-lake-id="u9e4667dd" id="u9e4667dd">一般来说，根据对账的时机分为两种，离线对账和（准）实时对账。</span></p>
  <p data-lake-id="ue0cd9c7b" id="ue0cd9c7b"><span data-lake-id="u9c69b3e2" id="u9c69b3e2">​</span><br></p>
  <p data-lake-id="ua202c0f6" id="ua202c0f6"><span data-lake-id="u4184025d" id="u4184025d">实时对账，一般来说基本都是准实时，也就是说并不能保证无延迟，但是一般可以控制在秒级别的延迟上。而离线对账一般都是D+1的核对。</span></p>
  <p data-lake-id="u06139758" id="u06139758"><span data-lake-id="ue114c42f" id="ue114c42f">​</span><br></p>
  <blockquote data-lake-id="u38c4046b" id="u38c4046b">
   <p data-lake-id="ufa1d7e1b" id="ufa1d7e1b"><span data-lake-id="u9d1f4c4f" id="u9d1f4c4f">D+1：D指的是自然日，包括工作日和节假日。+1指的是第二天，也就是说数据发生后的第二天进行核对。</span></p>
   <p data-lake-id="ua5f5d645" id="ua5f5d645"><span data-lake-id="u7aa1d090" id="u7aa1d090">T+1 ：T 指的是交易日，一般来说就是工作日，所以T+1指的就是数据发生后的下一个工作日进行核对。</span></p>
  </blockquote>
  <p data-lake-id="uedd1322a" id="uedd1322a"><span data-lake-id="ue6432bfb" id="ue6432bfb">​</span><br></p>
  <p data-lake-id="u6eaf2467" id="u6eaf2467"><strong><span data-lake-id="u62abc979" id="u62abc979">那么对账的技术实现上一般主要就是两种，要么是写代码核对，要么是写SQL核对。</span></strong></p>
  <p data-lake-id="ub67b3537" id="ub67b3537"><span data-lake-id="ua3458fa4" id="ua3458fa4">​</span><br></p>
  <p data-lake-id="ub8ddb3ac" id="ub8ddb3ac"><span data-lake-id="u8d844485" id="u8d844485">写代码就是查出需要比对的两条记录，然后进行字段的比对，不一致的就抛出来。</span></p>
  <p data-lake-id="u8f2faa7c" id="u8f2faa7c"><span data-lake-id="ud3fc9748" id="ud3fc9748">写SQL就是做join查询或者子查询，然后通过where条件比较需要核对的字段，不一致的就抛出来。</span></p>
  <p data-lake-id="ufbc1f6ec" id="ufbc1f6ec"><span data-lake-id="uaadc8e72" id="uaadc8e72">​</span><br></p>
  <p data-lake-id="u579e8d5c" id="u579e8d5c"><span data-lake-id="u98cf1be8" id="u98cf1be8">写代码这种核对方式，一般来说都是通过定时任务实现的，通过运行定时任务，然后去扫表，或者去远程拉数据，然后在业务代码中进行核对，这种方式的优点就是比较通用，不管是数据库，还是文件，还是远程接口，都可以做核对。缺点就是一旦数据量大了，代码核对的时效性就会比较差，而且代码运行存在失败的可能。万一数据量特别大，就可能会出现扫表扫不动，文件加载到内存导致OOM等问题。</span></p>
  <p data-lake-id="u45104a05" id="u45104a05"><span data-lake-id="u88f3708e" id="u88f3708e">​</span><br></p>
  <p data-lake-id="ub2c46f0f" id="ub2c46f0f"><span data-lake-id="ub1f5abaf" id="ub1f5abaf">所以，写代码的核对方式不推荐。</span></p>
  <p data-lake-id="u5ce00a05" id="u5ce00a05"><span data-lake-id="u64a17565" id="u64a17565">​</span><br></p>
  <p data-lake-id="uc8fea910" id="uc8fea910"><span data-lake-id="u094cb226" id="u094cb226">那么，更好一点的方式就是写SQL了，因为数据都在数据库（数仓或者大数据框架）中，这些都可以通过SQL进行查询，如下就是比较两个系统中金额是否一致的SQL：</span></p>
  <p data-lake-id="u8a9bf06c" id="u8a9bf06c"><span data-lake-id="uf6b32ce9" id="uf6b32ce9">​</span><br></p>
  <pre lang="java"><code>
SELECT out_biz_no,bill_no,owner FROM bill_item where out_biz_no in (
  select biz_id from collection_case_item_detail where case_item_state = "COLLECTING" and cur_ovd_principal &gt; 0
) and charge_on_amount - charge_off_amount = 0 
</code></pre>
  <p data-lake-id="u53b361ed" id="u53b361ed"><br></p>
  <p data-lake-id="u542105e0" id="u542105e0"><span data-lake-id="ue489d45a" id="ue489d45a">那在哪写这些SQL进行核对，就有很多种方案了，一般来说有以下几个：</span></p>
  <p data-lake-id="u6c3c41db" id="u6c3c41db"><span data-lake-id="u9b6c5eeb" id="u9b6c5eeb">​</span><br></p>
  <p data-lake-id="uc9685566" id="uc9685566"><span data-lake-id="uc336aa8b" id="uc336aa8b">1、离线数仓，主要用于离线数据核对。我们现在每天会把需要离线存储的数据同步到数仓中，然后在数仓中写SQL进行数据的核对。</span></p>
  <p data-lake-id="uc04228e9" id="uc04228e9"><span data-lake-id="u6f5798b8" id="u6f5798b8">2、在线数据库，离线核对的话发现问题比较慢，好一点的方案是在在线库做核对，可以直接在数据库中写SQL，进行数据核对。为了避免数据核对影响真实业务，可以考虑在备库中执行SQL。但是有的时候数据核对可能是多个系统之间的，这时候就要做跨库join，但是并不是所有的数据库、所有的引擎都支持跨库join的。</span></p>
  <p data-lake-id="ud5d8cf7c" id="ud5d8cf7c"><span data-lake-id="ua12fe362" id="ua12fe362">3、准实时数据库，还有一种方案，那就是不直接在数据库中写SQL，而是把数据同步到其他的地方，比如通过监听binlog的方式，把MYSQL的数据同步到实时数仓，比如我们公司内部用的就是AnalyticDB，把需要做核对的数据同步到ADB中，我们会尽量放到一个空间下面，然后在这里面写SQL作核对。同步出来的这个ADB数据，不仅可以做核对，还可以用于查询或者做报表。</span></p>
  <p data-lake-id="u27da660e" id="u27da660e"><span data-lake-id="ue2a886a5" id="ue2a886a5">4、ETL核对，还有一种比较常见的方案，那就是通过ETL工具进行数据核对，ETL包括了数据的提取、清洗、转化及加工，所以在这个过程中也是可以做核对的。</span></p>
  <p data-lake-id="u2e37b8ea" id="u2e37b8ea"><span data-lake-id="uf51023d1" id="uf51023d1">5、flink核对，flink是一个非常牛X的流处理框架，可以通过flink进行数据核对。</span></p>
  <p data-lake-id="ue2811bbb" id="ue2811bbb"><span data-lake-id="ue1cf6c4b" id="ue1cf6c4b">​</span><br></p>
  <p data-lake-id="u1e4681ce" id="u1e4681ce"><br></p>
  <h1 data-lake-id="dCYnD" id="dCYnD"><span data-lake-id="u6eb8f61c" id="u6eb8f61c">扩展知识</span></h1>
  <p data-lake-id="uce2b8ef2" id="uce2b8ef2"><br></p>
  <h2 data-lake-id="nQoCv" id="nQoCv"><span data-lake-id="u50e4e68e" id="u50e4e68e">最佳实践</span></h2>
  <p data-lake-id="u68e7a24c" id="u68e7a24c"><br></p>
  <p data-lake-id="u2b6c6cef" id="u2b6c6cef"><span data-lake-id="uabdddf4b" id="uabdddf4b">我在阿里待了8年，见证了我司的准实时核对系统的几代更迭。离线的核对基本就是离线数仓核对了，没啥变化。</span></p>
  <p data-lake-id="u66072e4d" id="u66072e4d"><span data-lake-id="u06058ed8" id="u06058ed8">​</span><br></p>
  <p data-lake-id="u65699712" id="u65699712"><span data-lake-id="u3b077753" id="u3b077753">最开始监听binlog，然后把binlog转成事件，有一个核对系统专门监听这个事件，然后我们自己在上面写Java代码实现数据核对。</span></p>
  <p data-lake-id="u79577814" id="u79577814"><span data-lake-id="u23ad57fb" id="u23ad57fb">​</span><br></p>
  <p data-lake-id="u8e882f8b" id="u8e882f8b"><span data-lake-id="ucb801317" id="ucb801317">后来直连数据库的备库，直接通过写SQL语句的方式做核对。</span></p>
  <p data-lake-id="u294362c0" id="u294362c0"><span data-lake-id="ub8d67a36" id="ub8d67a36">​</span><br></p>
  <p data-lake-id="ue1bd4c30" id="ue1bd4c30"><span data-lake-id="ub610cd11" id="ub610cd11">到现在基于flink监听数据库变更，然后把数据写入adb中，核对平台直连ADB，写SQL语句做核对。</span></p>
  <p data-lake-id="u2b294a58" id="u2b294a58"><span data-lake-id="u5668771e" id="u5668771e">​</span><br></p>
  <p data-lake-id="u52a5f6cb" id="u52a5f6cb"><span data-lake-id="u3bbdd5f3" id="u3bbdd5f3">所以，我见过的这几个方案，应该也是目前业内比较流行的几种方案了。</span></p>
  <p data-lake-id="ubc9d7569" id="ubc9d7569"><span data-lake-id="u8715a07d" id="u8715a07d">​</span><br></p>
  <p data-lake-id="uc4908c1a" id="uc4908c1a"><span data-lake-id="uc2671514" id="uc2671514">​</span><br></p>
  <h2 data-lake-id="w3vfs" id="w3vfs"><span data-lake-id="udc420313" id="udc420313">核对与告警</span></h2>
  <p data-lake-id="u27938a25" id="u27938a25"><br></p>
  <p data-lake-id="u9cf4577c" id="u9cf4577c"><span data-lake-id="ue8f44491" id="ue8f44491">有朋友提问：如果通过sql语句发现有不一致的数据,一般是怎么处理?是发告警通知开发人员还是说需要将有问题的数据标记出来过段时间重新核对?</span></p>
  <p data-lake-id="ufeb0b738" id="ufeb0b738"><br></p>
  <p data-lake-id="u08377c79" id="u08377c79"><span data-lake-id="u36965303" id="u36965303">我们一般来说，告警的话，人工一定要跟进。</span></p>
  <p data-lake-id="u1eeaa99b" id="u1eeaa99b"><span data-lake-id="ud9741234" id="ud9741234">​</span><br></p>
  <p data-lake-id="u8cdd8b1f" id="u8cdd8b1f"><span data-lake-id="ubbb80ed9" id="ubbb80ed9">至于说过段时间重新核对，这种我们一般在报警规则中把他屏蔽掉了。有需要重新核对的，我们会做一些延迟核对，或者二次核对。或者直接报警规则设置成多次失败后再报警，或者告警阈值调高一点。</span></p>
  <p data-lake-id="ue07e5db4" id="ue07e5db4"><span data-lake-id="u59eab232" id="u59eab232">​</span><br></p>
  <p data-lake-id="u4f9ca7c5" id="u4f9ca7c5"><span data-lake-id="u88e02973" id="u88e02973">这样就是</span><strong><span data-lake-id="u9ec6b155" id="u9ec6b155">职责单一</span></strong><span data-lake-id="uc6208332" id="uc6208332">，核对就是核对，报警就是报警。有报警人就要看，否则都是无效报警，那有效报警也没人看了。</span></p>
 </body>
</html>